package com.jxpay.fupay.core;

//import org.apache.commons.codec.binary.Base64;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import com.alibaba.fastjson.JSONObject;


/**
 * 签名验签工具
 * 
 * @author chang
 *
 */
public class SignUtils {

    public static String run(Map<String, String> map, String url,String charset,String privateKey,String publicKey) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
        Map<String, String> reqs = new HashMap<>();
        Map<String, String> nvs = new HashMap<>();

        reqs.putAll(map);

        String sign = getSign(charset,reqs,privateKey);
        reqs.put("sign", sign);

        Document doc = DocumentHelper.createDocument();
        Element root = doc.addElement("xml");

        Iterator it=reqs.keySet().iterator();
        while(it.hasNext()){
            String key = it.next().toString();
            String value = reqs.get(key);
            if (value==null) {
            	root.addElement(key).addText("");	
            }
            else {
              root.addElement(key).addText(value);
            }
        }

        String reqBody = "<?xml version=\"1.0\" encoding=\"GBK\" standalone=\"yes\"?>" + doc.getRootElement().asXML();

        //System.out.println("==============================待编码字符串==============================\r\n" + reqBody);

        reqBody = URLEncoder.encode(reqBody, charset);

        //System.out.println("==============================编码后字符串==============================\r\n" + reqBody);

        nvs.put("req", reqBody);

        StringBuffer result = new StringBuffer("");

        HttpUtils httpUtils = new HttpUtils();
        httpUtils.post(url, nvs, result);
       // System.out.println("==============================输出字符串==============================\r\n" + URLDecoder.decode(result.toString(), FuiouConstant.charset));
        String ret=URLDecoder.decode(result.toString(),charset);

        return ret;
    }
    
    
    public static Map<String, String> paraFilter(Map<String, String> map) {

        Map<String, String> result = new HashMap<>();

        if (map == null || map.size() <= 0) {
            return result;
        }

        for (String key : map.keySet()) {
            String value = map.get(key);
            if (key.equalsIgnoreCase("sign") || (key.length() >= 8 && key.substring(0, 8).equalsIgnoreCase("reserved"))) {
                continue;
            }
            result.put(key, value);
        }

        return result;
    }

    public static String createLinkString(Map<String, String> map) {

        List<String> keys = new ArrayList<>(map.keySet());
        Collections.sort(keys);

        String prestr = "";

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = map.get(key);

            if (i == keys.size() - 1) {
                //拼接时，不包括最后一个&字符
                prestr = prestr + key + "=" + value;
            } else {
                prestr = prestr + key + "=" + value + "&";
            }
        }

        return prestr;
    }

    public static String getSign(String charset,Map<String, String> map,String privateKey) throws InvalidKeySpecException, SignatureException, NoSuchAlgorithmException, InvalidKeyException, IOException {

        Map<String, String> mapNew = paraFilter(map);

        String preSignStr = createLinkString(mapNew);

       // System.out.println("==============================待签名字符串==============================\r\n" + preSignStr);
       // System.out.println("==============================privateKey==============================\r\n" + privateKey);
        String sign = sign(charset,preSignStr, privateKey);

        sign = sign.replace("\r\n", "");

        //System.out.println("==============================签名字符串==============================\r\n" + sign);

        return sign;
    }

    public static Boolean verifySign(Map<String, String> map, String sign,String publicKey) throws Exception {

        Map<String, String> mapNew = paraFilter(map);

        String preSignStr = createLinkString(mapNew);

        return verify(preSignStr.getBytes(), publicKey, sign);
    }
    
    public static Map<String,String> xmlStr2Map(String xmlStr){
        Map<String,String> map = new HashMap<String,String>();
        Document doc;
        try {
            doc = DocumentHelper.parseText(xmlStr);
            Element resroot = doc.getRootElement();
            List children = resroot.elements();
            if(children != null && children.size() > 0) {
                for(int i = 0; i < children.size(); i++) {
                    Element child = (Element)children.get(i);
//                    map.put(child.getName(), child.getTextTrim());
                    map.put(child.getName(), child.getStringValue().trim());
                }
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return map;
    }

    public static void main(String[] args) {
        String xmlStr = "<?xml version=\"1.0\" encoding=\"GBK\" standalone=\"yes\"?>\n" +
                "<xml>\n" +
                "    <sign>1\n2 3   4:5</sign>\n" +
                "</xml>";
        xmlStr2Map(xmlStr);
    }




    public static JSONObject xml2JSON(byte[] xml) throws JDOMException, IOException {
        InputStream is = new ByteArrayInputStream(xml);
        SAXBuilder sb = new SAXBuilder();
        org.jdom.Document doc = sb.build(is);
        org.jdom.Element root = doc.getRootElement();
//        List<org.jdom.Element> list = root.getChildren("xml");
//        for(org.jdom.Element el : list){
//            if (el.getAttributeValue("xml").equals("xml")) {
//                root.removeContent(el);
//            }
//        }
        return iterateElement(root);
    }

    private static JSONObject iterateElement(org.jdom.Element element) {
        List node = element.getChildren();
        org.jdom.Element et = null;
        JSONObject obj = new JSONObject();
        List list = null;
        for (int i = 0; i < node.size(); i++) {
            list = new LinkedList();
            et = (org.jdom.Element) node.get(i);
            if (et.getTextTrim().equals("")) {
                if (et.getChildren().size() == 0)
                    continue;
                if (obj.containsKey(et.getName())) {
                    list = (List) obj.get(et.getName());
                }
                list.add(iterateElement(et));
                obj.put(et.getName(), list);
            } else {
                if (obj.containsKey(et.getName())) {
                    list = (List) obj.get(et.getName());
                }
                if (et.getChildren().size() != 0) {
                    list.add(et.getTextTrim());
                    obj.put(et.getName(), list);
                } else {
                    obj.put(et.getName(), et.getTextTrim());
                }
            }
        }
        return obj;
    }

    public static Map<String, String> mapFilter(Map<String, String> map) {

        Map<String, String> result = new HashMap<>();

        if (map == null || map.size() <= 0) {
            return result;
        }

        for (String key : map.keySet()) {
            String value = map.get(key);
            if (key.equalsIgnoreCase("sign") || (key.length() >= 8 && key.substring(0, 8).equalsIgnoreCase("reserved"))) {
                continue;
            }
            if ("".equalsIgnoreCase(value) || null == value){
                continue;
            }
            result.put(key, value);
        }

        return result;
    }


    public static String fuiouConfigXml(String charset,Map<String, String> map, String url,String configKey) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException, InvalidKeyException, SignatureException {
        Map<String, String> reqs = new LinkedHashMap<>();
        Map<String, String> nvs = new HashMap<>();
        reqs.putAll(map);
        configKey = "&key="+configKey;
        Map<String, String> filterMap = mapFilter(reqs);//去除Map集合中的空串
        String preSignStr = createLinkString(filterMap);//拼接待签名字符串
        String signStr = preSignStr + configKey;//待编码字符串
        String sign = MD5Utils.MD5Encode(signStr,charset);
        reqs.put("sign", sign);

        Document doc = DocumentHelper.createDocument();
        Element root = doc.addElement("xml");

        Iterator it=reqs.keySet().iterator();
        while(it.hasNext()){
            String key = it.next().toString();
            String value = reqs.get(key);
            if (value==null) {
                root.addElement(key).addText("");
            }
            else {
                root.addElement(key).addText(value);
            }
        }

        String reqBody = "<?xml version=\"1.0\" encoding=\"GBK\" standalone=\"yes\"?>" + doc.getRootElement().asXML();

       // log.info("==============================待编码字符串==============================\r\n" + reqBody);

        reqBody = URLEncoder.encode(reqBody, charset);

       // log.info("==============================编码后字符串==============================\r\n" + reqBody);

        nvs.put("req", reqBody);

        StringBuffer stringBuffer = new StringBuffer("");

        HttpUtils httpUtils = new HttpUtils();
        httpUtils.post(url, nvs, stringBuffer);
        System.out.println("==============================输出字符串==============================\r\n" + URLDecoder.decode(stringBuffer.toString(), charset));
        String result=URLDecoder.decode(stringBuffer.toString(),charset);
        return result;
    }


    public static String fuiouConfigJson(Map<String, String> map, String url,String configKey,String charset) throws IOException {
        Map<String, String> reqs = new LinkedHashMap<>();
        reqs.putAll(map);
        configKey = "&key="+configKey;
        Map<String, String> filterMap = mapFilter(reqs);//去除Map集合中的空串
        String preSignStr = createLinkString(filterMap);//拼接待签名字符串
        String signStr = preSignStr + configKey;//待编码字符串
        String sign = MD5Utils.MD5Encode(signStr,charset);
        reqs.put("sign", sign);
//        StringBuffer stringBuffer = new StringBuffer();
//        HttpUtils httpUtils = new HttpUtils();
//        httpUtils.post(url, JSONObject.toJSONString(reqs), stringBuffer);
//        return stringBuffer.toString();
        Map requestMap = new HashMap();//去除Map集合中的空串
        //log.info("==============================JSON请求字符串==============================\r\n" + JSONObject.toJSONString(reqs));
        requestMap.put("req",JSONObject.toJSONString(reqs));
        return HttpClientUtil.postJsonWithMap(url, requestMap);
    }

    
    /**
     * sign(RSA签名方法)
     * TODO(这里描述这个方法适用条件 – 可选)
     *
     * @param srcSignPacket
     * @param privateKey
     * @return String    DOM对象
     * @Exception 异常对象
     */
    public static String sign(String charset,String srcSignPacket, String privateKey)
            throws IOException, NoSuchAlgorithmException,
            InvalidKeySpecException, InvalidKeyException, SignatureException {
        // 解密由base64编码的私钥
        byte[] bytesKey = (new BASE64Decoder()).decodeBuffer(privateKey);
        // 构造PKCS8EncodedKeySpec对象
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(bytesKey);
        // KEY_ALGORITHM 指定的加密算法
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        // 取私钥匙对象
        PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
        // 用私钥对信息生成数字签名
        java.security.Signature signature = java.security.Signature.getInstance("MD5WithRSA");
        signature.initSign(priKey);
        signature.update(srcSignPacket.getBytes(charset));
        String sign = (new BASE64Encoder()).encodeBuffer(signature.sign());
        return sign;
    }

    /**
     * 校验数字签名
     *
     * @param data      加密数据
     * @param publicKey 公钥
     * @param sign      数字签名
     * @return 校验成功返回true 失败返回false
     * @throws Exception
     */
    public static boolean verify(byte[] data, String publicKey, String sign)
            throws Exception {

        // 解密由base64编码的公钥
        byte[] keyBytes = decryptBASE64(publicKey);

        // 构造X509EncodedKeySpec对象
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);

        // KEY_ALGORITHM 指定的加密算法
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");

        // 取公钥匙对象
        PublicKey pubKey = keyFactory.generatePublic(keySpec);

        Signature signature = Signature.getInstance("MD5withRSA");
        signature.initVerify(pubKey);
        signature.update(data);

        // 验证签名是否正常
        return signature.verify(decryptBASE64(sign));
    }

    /**
     * BASE64加密
     * @param key
     * @return
     * @throws Exception
     */
    public static String encryptBASE64(byte[] key) throws Exception {
        return (new BASE64Encoder()).encodeBuffer(key);
    }

    /**
     * BASE64解密
     *
     * @param key
     * @return
     * @throws Exception
     */
    public static byte[] decryptBASE64(String key) throws Exception {
        return (new BASE64Decoder()).decodeBuffer(key);
    }    
}
